﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace VirtualEnvironmentXNA.Cameras
{
    /// <summary>
    /// A camera that can be visualized
    /// </summary>
    public class VisualCamera : Camera
    {
        #region Fields

        protected DrawableObject cam;

        // Vertices & Indices
        protected VertexPositionColor[] cameraFrustumCornersVerts = new VertexPositionColor[9];
        protected VertexPositionTexture[] nearVerts = new VertexPositionTexture[4];

        protected Vector3[] cameraFrustumConrners = new Vector3[8];

        protected IndexBuffer frustumIndexBufferLines, frustumIndexBufferTriangles;

        protected DynamicVertexBuffer frustumVertexBuffer;

        protected XNAGame game;
        protected Viewport viewport;

        protected BasicEffect effect;

        protected RasterizerState rasState, origRasState;
        protected BlendState origBlendState;

        protected VertexPositionColor[] mouseRayVerts = new VertexPositionColor[2];

        #endregion

        #region Initialization

        public VisualCamera(Vector3 position, Vector3 target, float near, float far, Model camModel, float modelScale, Matrix modelRotation, Viewport viewport, XNAGame game)
            : base(position, target, near, far, game)
        {
            this.game = game;
            this.viewport = viewport;

            if (camModel != null)
                cam = new DrawableObject(camModel, position, modelRotation, modelScale);

            effect = new BasicEffect(device);
            effect.VertexColorEnabled = true;


            #region Vertices Initialization

            for (int i = 0; i <= cameraFrustumCornersVerts.Length - 1; i++)
            {
                cameraFrustumCornersVerts[i] = new VertexPositionColor(Vector3.Zero, new Color(0, 0, 200, 50));
            }

            GetFrustum.GetCorners(cameraFrustumConrners);
            for (int i = 0; i <= 7; i++)
            {
                cameraFrustumCornersVerts[i].Position = cameraFrustumConrners[i];
            }


            nearVerts[0] = new VertexPositionTexture(cameraFrustumCornersVerts[0].Position, new Vector2(0, 0));
            nearVerts[1] = new VertexPositionTexture(cameraFrustumCornersVerts[3].Position, new Vector2(0, 1));
            nearVerts[2] = new VertexPositionTexture(cameraFrustumCornersVerts[1].Position, new Vector2(1, 0));
            nearVerts[3] = new VertexPositionTexture(cameraFrustumCornersVerts[2].Position, new Vector2(1, 1));

            mouseRayVerts[0] = new VertexPositionColor(Vector3.Zero, Color.Red);
            mouseRayVerts[1] = new VertexPositionColor(Vector3.Zero, Color.Red);

            #endregion

            #region Indices Initialization

            rasState = new RasterizerState();
            rasState.CullMode = CullMode.None;

            ushort[] frustumIndicesLines, frustumIndicesTriangles;

            frustumIndicesLines = new ushort[] {
                            // near plane
                            0,1,
                            1,2,
                            2,3,
                            3,0,

                            // far plane
                            4,5,
                            5,6,
                            6,7,
                            7,4,

                            // from position to far plane
                            8,4,
                            8,5,
                            8,6,
                            8,7,
            };

            frustumIndicesTriangles = new ushort[] {
                // Near
                0,1,2,
                0,2,3,

                // Far
                6,5,4,
                7,6,4,

                // Up
                4,1,0,
                4,5,1,

                // Down
                7,3,2,
                2,6,7,

                // Left
                0,3,4,
                7,4,3,

                // Right
                5,2,1,
                5,6,2

            };

            frustumVertexBuffer = new DynamicVertexBuffer(device, VertexPositionColor.VertexDeclaration, cameraFrustumCornersVerts.Length, BufferUsage.WriteOnly);
            frustumVertexBuffer.SetData(cameraFrustumCornersVerts);
            frustumVertexBuffer.ContentLost += new EventHandler<EventArgs>(frustumVertexBuffer_ContentLost);

            frustumIndexBufferTriangles = new IndexBuffer(device, typeof(ushort), frustumIndicesTriangles.Length, BufferUsage.WriteOnly);
            frustumIndexBufferTriangles.SetData(frustumIndicesTriangles);

            frustumIndexBufferLines = new IndexBuffer(device, typeof(ushort), frustumIndicesLines.Length, BufferUsage.WriteOnly);
            frustumIndexBufferLines.SetData(frustumIndicesLines);

            #endregion
        }

        #endregion

        #region Update & Draw

        public override void Update()
        {
            // Updating Vertices
            GetFrustum.GetCorners(cameraFrustumConrners);
            for (int i = 0; i <= 7; i++)
            {
                cameraFrustumCornersVerts[i].Position = cameraFrustumConrners[i];
            }
            cameraFrustumCornersVerts[8].Position = position;

            nearVerts[0].Position = cameraFrustumCornersVerts[0].Position;
            nearVerts[1].Position = cameraFrustumCornersVerts[3].Position;
            nearVerts[2].Position = cameraFrustumCornersVerts[1].Position;
            nearVerts[3].Position = cameraFrustumCornersVerts[2].Position;

            frustumVertexBuffer.SetData(cameraFrustumCornersVerts);

            mouseRayVerts[0].Position = mouseRay.Position;
            mouseRayVerts[1].Position = mouseRay.Position + farPlane * mouseRay.Direction;

            if (cam != null)
            {
                cam.Position = position;
                //cam.Rotation = Matrix.CreateFromYawPitchRoll(base.yaw, -base.pitch, 0);

                Matrix rot = cam.Rotation;
                rot.Forward = this.Direction;
                rot.Right = Vector3.Normalize(Vector3.Cross(rot.Forward, Vector3.Up)); // If we use rot.Up instead of Vector3.Up the model may roll! which's not what we want in this case
                rot.Up = Vector3.Normalize(Vector3.Cross(rot.Right, rot.Forward));

                cam.Rotation = rot;
            }

            base.Update();
        }

        public virtual void Draw(Camera camera, Texture2D projectedScene)
        {
            if (cam != null) cam.Draw(camera);

            effect.Projection = camera.Projection;
            effect.View = camera.View;
            effect.World = Matrix.Identity;
            effect.Texture = projectedScene;

            // Save states before changing them
            origRasState = device.RasterizerState;
            origBlendState = device.BlendState;
            device.RasterizerState = rasState;


            device.BlendState = BlendState.Opaque;
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                // Frustum wires
                device.SetVertexBuffer(frustumVertexBuffer);
                device.Indices = frustumIndexBufferLines;
                device.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0, cameraFrustumCornersVerts.Length, 0, 12);

                // Mouse Ray
                device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, mouseRayVerts, 0, 1);

                // Frustum filled
                device.BlendState = BlendState.AlphaBlend;
                device.SetVertexBuffer(frustumVertexBuffer);
                device.Indices = frustumIndexBufferTriangles;
                int startIndex = (projectedScene == null ? 0 : 6);
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, cameraFrustumCornersVerts.Length, startIndex, 12);
            }

            // Mapping projected scene (if provided) to the near plane
            // Using textures only without disabling colors will result in errors
            device.BlendState = BlendState.Additive;
            if (projectedScene != null)
            {
                effect.VertexColorEnabled = false;
                effect.TextureEnabled = true;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, nearVerts, 0, 2);
                }
                effect.TextureEnabled = false;
                effect.VertexColorEnabled = true;
            }


            // Restore states
            device.RasterizerState = origRasState;
            device.BlendState = origBlendState;
            device.SetVertexBuffer(null);
        }

        #endregion

        #region Misc. Functions

        protected void frustumVertexBuffer_ContentLost(object sender, EventArgs e)
        {
            frustumVertexBuffer.SetData(cameraFrustumCornersVerts);
        }

        #endregion

    }
}
